# coding=utf-8


import traceback
from controller.controller_base import *
from dbs.models import *
from inspect import isfunction
from dbs.models import Task
from flask import current_app
import json, os, re
import threading

from conf import constant, settings, json_map


class TaskController(ControllerBase):
    map_table = {GEO_LAYER_BRIDGE: Bridge, GEO_LAYER_BUILDING: Building, GEO_LAYER_CULVERT: Culvert,
                 GEO_LAYER_DECELERATION: Deceleration, GEO_LAYER_DELINEATER: Delineater, GEO_LAYER_GUARDBAR: GuardBar,
                 GEO_LAYER_ILLUMINATION: Illumination, GEO_LAYER_LANDUSE: LandUse, GEO_LAYER_ROADLINE: RoadLine,
                 GEO_LAYER_ROADPOLYGON: RoadPolygon, GEO_LAYER_ROADSIGN: RoadSign, GEO_LAYER_TREELAWN: TreeLawn,
                 GEO_LAYER_TUNNEL: Tunnel, GEO_LAYER_WATER: Water}

    def __init__(self):
        super(TaskController, self).__init__()

    def check_geo_layers(self, geo_layers):
        for geo_layer in geo_layers:
            model = self.map_table.get(geo_layer)
            if not model:
                raise Exception('geo_layer can not match a model')

    def __transform_db_arrays(self, results, deal_item_func=None):
        array = []
        for result in results:
            item = dict(zip(result.keys(), result))
            if isfunction(deal_item_func):
                deal_item_func(item)
            array.append(item)
        return array

    def __query_audit_info(self, task_id, sub_data_set_id):
        new_data = self.db.session.query(func.count(TemporaryData.data_id)) \
            .filter(TemporaryData.operate_type == OPERATE_TYPE_NEW, TemporaryData.task_id == task_id,
                    TemporaryData.submitted.is_(True)).scalar()
        modify_data = self.db.session.query(func.count(TemporaryData.data_id)) \
            .filter(TemporaryData.operate_type == OPERATE_TYPE_MODIFY, TemporaryData.task_id == task_id,
                    TemporaryData.submitted.is_(True)).scalar()
        delete_data = self.db.session.query(func.count(TemporaryData.data_id)) \
            .filter(TemporaryData.operate_type == OPERATE_TYPE_DELETE, TemporaryData.task_id == task_id,
                    TemporaryData.submitted.is_(True)).scalar()
        abnormal_data = self.db.session.query(func.count(TemporaryData.data_id)) \
            .filter(TemporaryData.check_status == CHECK_STATUS_ABNORMAL, TemporaryData.task_id == task_id,
                    TemporaryData.submitted.is_(True)).scalar()
        results = self.db.session.query(
            TemporaryData.geo_layer.label('geo_layer'),
            func.count(TemporaryData.data_id).label('count')
        ).filter(
            TemporaryData.task_id == task_id,
            TemporaryData.submitted.is_(True)
        ).group_by(TemporaryData.geo_layer).all()
        count = {}
        for item in results:
            count[str(item[0])] = item[1]
        return {
            'sub_data_set_id': sub_data_set_id,
            'new_data': new_data,
            'modify_data': modify_data,
            'delete_data': delete_data,
            'abnormal_data': abnormal_data,
            'count': count
        }

    def get(self, task_id):
        data = self.db.session.query(
            Task.task_name.label('task_name'),
            Task.task_status.label('task_status'),
            func.to_char(Task.create_time, "YYYY-MM-DD HH24:MI:SS").label('create_time'),
            func.to_char(Task.update_time, "YYYY-MM-DD HH24:MI:SS").label('update_time'),
            User.username.label('user_name')
        ).outerjoin(User, User.user_id == Task.user_id) \
            .filter(Task.task_id == task_id).first()
        if data:
            task_data = dict(zip(data.keys(), data))
            task_status = task_data.get('task_status')
            task_data['task_status_str'] = self.get_word_name(task_status)
            count = {}
            for key in self.map_table.keys():
                c = self.db.session.query(func.count(TemporaryData.data_id)) \
                    .join(TaskDataRelation, TaskDataRelation.data_id == TemporaryData.data_id) \
                    .filter(TemporaryData.geo_layer == int(key),
                            TaskDataRelation.task_id == task_id,
                            TaskDataRelation.is_deleted.is_(False)
                            ).first()[0]
                count[key] = c
            task_data['extend_info'] = {'count': count}
            return task_data
        else:
            raise Exception('can not find data with task_id ' + task_id)

    # def get_task_contents(self, task_id, page_index, page_size, for_audit=False, geo_layers=None,
    #                       audit_status=None, check_status=None):
    #
    #     def item_transform(item):
    #         item['geo_json'] = json.loads(item.get('geo_json'))
    #         item['check_status_str'] = self.get_word_name(item.get('check_status'))
    #         item['operate_type_str'] = self.get_word_name(item.get('operate_type'))
    #         item['audit_status_str'] = self.get_word_name(item.get('audit_status'))
    #         item['geo_layer_str'] = self.get_word_name(item.get('geo_layer'))
    #
    #     data = self.db.session.query(
    #         Task.task_status.label('task_status')
    #     ).filter(Task.task_id == task_id).first()
    #     if data:
    #         filters = {
    #             TemporaryData.task_id == task_id,
    #             TemporaryData.geo_layer.in_(geo_layers or []),
    #         }
    #         if for_audit:
    #             filters.add(TemporaryData.submitted.is_(True))
    #         if audit_status:
    #             filters.add(TemporaryData.audit_status == audit_status)
    #         if check_status:
    #             filters.add(TemporaryData.check_status == check_status)
    #         result = db.session.query(
    #             TemporaryData.data_id.label('data_id'),
    #             TemporaryData.gis_id.label('gis_id'),
    #             TemporaryData.geo_layer.label('geo_layer'),
    #             TemporaryData.geo_type.label('geo_type'),
    #             TemporaryData.gis_class.label('gis_class'),
    #             TemporaryData.operate_type.label('operate_type'),
    #             TemporaryData.check_status.label('check_status'),
    #             TemporaryData.submitted.label('submitted'),
    #             TemporaryData.audit_status.label('audit_status'),
    #             func.to_char(TemporaryData.update_time, "YYYY-MM-DD HH24:MI:SS").label('update_time'),
    #             TemporaryData.geom.ST_AsGeoJSON().label('geo_json'),
    #             TemporaryData.audit_remark.label('audit_remark'),
    #         ).filter(*filters) \
    #             .paginate(page_index, page_size)
    #         # .order_by(TemporaryData.audit_status.desc(), TemporaryData.check_status.desc()) \
    #         array = self.__transform_db_arrays(result.items, item_transform)
    #         pagination = {
    #             'page_count': result.pages,
    #             'page_index': page_index,
    #             'page_size': page_size,
    #             'total': result.total
    #         }
    #         return array, pagination
    #     else:
    #         raise Exception('can not find data with task_id: ' + str(task_id))

    def get_task_contents(self, task_id, page_index, page_size, geo_layers=None,
                          audit_status=None, is_modify=None):
        def item_transform(item):
            item['geo_json'] = json.loads(item.get('geo_json'))
            item['geo_layer_str'] = self.get_word_name(item.get('geo_layer'))

        data_ids_filters = {
            TaskDataRelation.task_id == task_id,
            TaskDataRelation.is_deleted.is_(False)
        }
        if audit_status:
            data_ids_filters.add(TaskDataRelation.audit_status == audit_status)
        data_ids_res = self.db.session.query(TaskDataRelation.data_id).filter(*data_ids_filters).all()

        if len(data_ids_res) == 0:
            return False, "没有数据"
        data_ids = list(data_ids_res)

        temporary_data_filters = {
            TemporaryData.data_id.in_(data_ids),
            TemporaryData.geo_layer.in_(geo_layers or []),
            TaskDataRelation.is_deleted.is_(False),
            TaskDataRelation.task_id == task_id,
        }

        if is_modify is not None:
            if is_modify:
                temporary_data_filters.add(TaskDataRelation.is_modify.is_(True))
            else:
                temporary_data_filters.add(TaskDataRelation.is_modify.is_(False))

        result = self.db.session.query(
            TemporaryData.data_id.label('data_id'),
            TemporaryData.gis_id.label('gis_id'),
            TemporaryData.geo_layer.label('geo_layer'),
            TemporaryData.geo_type.label('geo_type'),
            TemporaryData.gis_class.label('gis_class'),
            TemporaryData.check_status.label('check_status'),
            func.to_char(TemporaryData.update_time, "YYYY-MM-DD HH24:MI:SS").label('update_time'),
            TemporaryData.geom.ST_AsGeoJSON().label('geo_json'),
            TemporaryData.is_modify.label('is_modify'),
            TaskDataRelation.audit_remark.label('audit_remark')
        ).join(TaskDataRelation, TaskDataRelation.data_id == TemporaryData.data_id) \
            .filter(*temporary_data_filters) \
            .paginate(page_index, page_size)
        array = self.__transform_db_arrays(result.items, item_transform)
        pagination = {
            'page_count': result.pages,
            'page_index': page_index,
            'page_size': page_size,
            'total': result.total
        }
        return True, {'list': array, 'pagination': pagination}

    def get_real(self, params, x_min, y_min, x_max, y_max):
        def item_transform(item):
            item['geo_json'] = json.loads(item.get('geo_json'))

        res = []
        for param in params:
            sub_set_id = param.get('sub_set_id')
            for geo_layer in param.get('geo_layers'):
                model = self.map_table.get(geo_layer)
                results = self.db.session.query(
                    model.gis_id.label('gis_id'),
                    model.sub_set_id.label('sub_set_id'),
                    model.geo_layer.label('geo_layer'),
                    model.geom.ST_AsGeoJSON().label('geo_json')
                ).filter(
                    model.sub_set_id == sub_set_id,
                    func.ST_Contains(func.ST_MakeEnvelope(x_min, y_min, x_max, y_max, 3857), model.geom)
                ).all()

                array = self.__transform_db_arrays(results, item_transform)

                if array:
                    res.append(array)
        return res

    def get_statistics(self, task_id):
        pending_count = db.session.query(func.count(TemporaryData.data_id)).filter(
            TemporaryData.submitted.is_(False),
            TemporaryData.task_id == task_id
        ).one()[0]
        to_be_audited_count = db.session.query(func.count(TemporaryData.data_id)).filter(
            TemporaryData.submitted.is_(True),
            TemporaryData.audit_status == AUDIT_STATUS_NEW,
            TemporaryData.task_id == task_id
        ).one()[0]
        audited_count = db.session.query(func.count(TemporaryData.data_id)).filter(
            TemporaryData.audit_status == AUDIT_STATUS_AUDITED,
            TemporaryData.task_id == task_id
        ).one()[0]
        rejected_count = db.session.query(func.count(TemporaryData.data_id)).filter(
            TemporaryData.audit_status == AUDIT_STATUS_REJECTED,
            TemporaryData.task_id == task_id
        ).one()[0]
        return {
            'pending_count': pending_count,
            'to_be_audited_count': to_be_audited_count,
            'audited_count': audited_count,
            'rejected_count': rejected_count
        }

    def add_to_submit_list(self, data_ids, submitted):
        self.db.session.query(TemporaryData) \
            .filter(TemporaryData.data_id.in_(data_ids)) \
            .update({TemporaryData.submitted: submitted}, False)
        self.db.session.commit()

    def submit_to_audit(self, task_id):
        self.db.session.query(Task) \
            .filter(Task.task_id == task_id) \
            .update({Task.task_status: TASK_STATUS_SUBMITTED})
        self.db.session.commit()

    def recall_submit(self, task_id):
        self.db.session.query(Task) \
            .filter(Task.task_id == task_id) \
            .update({Task.task_status: TASK_STATUS_CANCELED})
        self.db.session.commit()

    def get_list(self, user_id, page_size, page_index, batch_id=None, status_list=None):
        def item_transform(item):
            item['task_status_str'] = self.get_word_name(item.get('task_status'))
            item['async_status_str'] = self.get_word_name(item.get('async_status'))

        filters = {
            Task.task_status.in_(status_list or []),
            Task.user_id == user_id,
            Task.is_deleted.is_(False),
            Batch.is_deleted.is_(False),
        }
        if batch_id:
            filters.add(Task.batch_id == batch_id)
        result = db.session.query(
            # TemporaryData.data_id.label('data_id'),
            Task.task_id.label('task_id'),
            Task.task_name.label('task_name'),
            Task.task_status.label('task_status'),
            Task.async_status.label('async_status'),
            Task.audit_remark.label('audit_remark'),
            func.to_char(Task.update_time, "YYYY-MM-DD HH24:MI:SS").label('update_time'),
            User.username.label('user_name'),
            SubDataSet.sub_set_name.label('sub_set_name')
        ).join(User, User.user_id == Task.user_id) \
            .join(Batch, Batch.batch_id == Task.batch_id) \
            .join(SubDataSet, SubDataSet.sub_set_id == Batch.sub_set_id) \
            .filter(*filters).order_by(Task.create_time.desc()) \
            .paginate(page_index, page_size)
        array = self.__transform_db_arrays(result.items, item_transform)
        pagination = {
            'page_count': result.pages,
            'page_index': page_index,
            'page_size': page_size,
            'total': result.total,
        }
        return array, pagination

    def get_audit_list(self, data_set_id, page_size, page_index, status_list=[]):
        task_list = []
        tasks = db.session.query(Task) \
            .outerjoin(SubDataSet, Task.sub_set_id == SubDataSet.sub_set_id) \
            .outerjoin(DataSet, SubDataSet.data_set_id == DataSet.data_set_id) \
            .filter(DataSet.data_set_id == data_set_id,
                    SubDataSet.is_deleted == False,
                    DataSet.is_deleted == False,
                    Task.task_status.in_(status_list)) \
            .order_by(Task.create_time.desc()) \
            .paginate(page_index, page_size, False)

        for task in tasks.items:
            task_dict = self.serialize(task)
            task_dict['sub_set_name'] = SubDataSet.query.filter_by(
                sub_set_id=task_dict.get('sub_set_id')).first().sub_set_name
            task_dict['user_name'] = User.query.filter_by(user_id=task_dict.get('user_id')).first().username
            task_list.append(task_dict)

        data = {
            'task_list': task_list,
            'pagination': {
                'page_index': page_index,
                'page_size': page_size,
                'page_count': tasks.pages,
                'total': tasks.total
            }
        }
        return data

    def add_task(self, user, task_name, batch_id):
        task = Task(
            user_id=user.user_id,
            batch_id=batch_id,
            task_name=task_name,
            task_status=constant.TASK_STATUS_NEW,
            extend_info={}
        )
        db.session.add(task)
        db.session.commit()
        db.session.close()  # 结束会话

    def remove_task(self, task_id):
        self.db.session.query(Task).filter_by(task_id=task_id).update({'is_deleted': True})
        self.db.session.commit()
        self.db.session.close()

    def get_gdb_list(self):
        """查看linux服务器 /data/gdb_files/里的文件列表"""
        result = []
        path = current_app.config['GDB_PATH']  # linux  gdb文件路径
        list_dir = os.listdir(path)  # linux 文件list
        for linux_file_name in list_dir:
            if not re.search('.gdb$', linux_file_name):
                writelog("服务器gdb文件目录中有非gdb文件:%s  请去查看 PATH:%s " % (linux_file_name, path))
            elif re.search('^\.', linux_file_name):
                writelog("服务器gdb文件目录中有隐藏文件:%s  请去查看 PATH:%s " % (linux_file_name, path))
            else:
                result.append(linux_file_name)
        return result

    """任务驳回 不通过审核"""

    def task_overruled(self, task_id, remark):
        try:
            influence_line = self.db.session.query(Task).filter(Task.task_id == task_id,
                                                                Task.task_status == TASK_STATUS_SUBMITTED). \
                update({"task_status": TASK_STATUS_REJECTED, "audit_remark": remark})
            task_data_relation_list = self.db.session.query(TaskDataRelation).filter(
                TaskDataRelation.task_id == task_id).all() or {}
            for task_data_relation in task_data_relation_list:
                task_data_relation.audit_status = AUDIT_STATUS_REJECTED
            db.session.commit()
            if influence_line > 0:
                return True
            else:
                return False
        except:
            writelog(traceback.format_exc())
            db.session.rollback()
            return False

    """检查这批临时数据是否有未通过审核的 如果有则所有数据都不允许加入正式数据中"""

    def check_if_there_is_any_not_passed(self, task_id):
        if self.db.session.query(TaskDataRelation).filter(TaskDataRelation.task_id == task_id,
                                                          TaskDataRelation.audit_status == constant.AUDIT_STATUS_REJECTED).all():
            return False
        else:
            return True

    """检查任务是否有效"""

    def is_the_inspection_task_valid(self, task_id):
        if self.db.session.query(Task).filter(Task.task_id == task_id,
                                              Task.task_status == TASK_STATUS_SUBMITTED).all():
            return True
        else:
            return False

    """审核单条数据"""

    def data_audit(self, task_id, data_list, adopt, remark):
        data_relation_list = self.db.session.query(TaskDataRelation). \
            filter(TaskDataRelation.task_id == task_id, TaskDataRelation.data_id.in_(data_list)).all()

        for data_relation in data_relation_list:
            data_relation.audit_status = adopt
            data_relation.audit_remark = remark
        self.db.session.commit()

        return True

    """任务通过审核"""

    def task_adopt(self, task_id):
        try:
            if not (self.check_if_there_is_any_not_passed(task_id)):
                return False, '任务数据中已有已驳回数据'
            if not (self.is_the_inspection_task_valid(task_id)):
                return False, '任务状态无效'
                # 查询临时数据列表
                # 然后将关联表的状态改为通过
            task_data_relation_list = self.db.session.query(TaskDataRelation).filter(
                TaskDataRelation.task_id == task_id
            ).all() or {}

            index = 0
            for task_data_relation in task_data_relation_list:
                if index <= 1000:
                    index += 1
                else:
                    index = 0

                temporary_data = self.db.session.query(TemporaryData).filter(
                    TemporaryData.data_id == task_data_relation.data_id).first()
                cls = self.map_table.get(temporary_data.geo_layer)
                # self.temporarily_converted_to_formal(obj, temporary_data)
                obj = self.db.session.query(cls).filter(
                    cls.gis_id == temporary_data.gis_id).first()
                # 查询该临时数据是否存在正式数据
                if obj:
                    # 存在则更新
                    obj.geom = temporary_data.geom
                    obj.gis_class = temporary_data.gis_class
                    obj.sub_set_id = temporary_data.sub_set_id
                    if 'height' in dir(obj):
                        obj.height = temporary_data.height
                else:
                    # 不存在则创建
                    obj = cls()
                    obj.gis_id = temporary_data.gis_id
                    obj.geom = temporary_data.geom
                    obj.gis_class = temporary_data.gis_class
                    obj.sub_set_id = temporary_data.sub_set_id
                    if 'height' in dir(obj):
                        obj.height = temporary_data.height
                    self.db.session.add(obj)
                task_data_relation.audit_status = AUDIT_STATUS_AUDITED

                if index == 1000:
                    self.db.session.commit()

            task = self.db.session.query(Task).filter(
                Task.task_id == task_id).first()
            task.task_status = TASK_STATUS_AUDITED
            self.db.session.commit()
            return True, '操作成功'
        except:
            writelog(traceback.format_exc())
            db.session.rollback()
        return False, '操作失败'

    def edit(self, task_id, task_name):
        self.db.session.query(Task) \
            .filter(Task.task_id == task_id) \
            .update({"task_name": task_name, })
        self.db.session.commit()

    def delete(self, task_id):
        task_status = self.db.session.query(Task.task_status).filter(Task.task_id == task_id).first()[0]
        if task_status == constant.TASK_STATUS_NEW or task_status == constant.TASK_STATUS_CANCELED:
            self.db.session.query(Task) \
                .filter(Task.task_id == task_id,
                        Task.task_status.in_([constant.TASK_STATUS_NEW, constant.TASK_STATUS_CANCELED])) \
                .update({"is_deleted": True, }, synchronize_session=False)
            self.db.session.commit()
            return True, "删除成功"
        else:
            return False, "只有新建和撤回的任务可以删除"

    def add_data_to_task(self, task_id, data_ids):
        count = self.db.session.query(func.count(TaskDataRelation.task_id)) \
            .join(Task, Task.task_id == TaskDataRelation.task_id) \
            .filter(TaskDataRelation.data_id.in_(data_ids),
                    TaskDataRelation.audit_status.in_([constant.AUDIT_STATUS_NEW, constant.AUDIT_STATUS_REJECTED]),
                    TaskDataRelation.is_deleted.is_(False),
                    Task.is_deleted.is_(False)) \
            .first()[0]
        if count > 0:
            return False, "部分数据已经存在于任务中"

        index = 0
        for data_id in data_ids:
            task_data_relation = TaskDataRelation(
                task_id=task_id,
                data_id=data_id,
            )
            self.db.session.add(task_data_relation)

            # 达到1000提交一次
            index += 1
            if index == 1000:
                self.db.session.commit()
                index = 0

        self.db.session.commit()
        return True, None

    def add_all_data_to_task(self, task_id, batch_id):
        data_ids_res = self.db.session.query(TemporaryData.data_id) \
            .filter(TemporaryData.batch_id == batch_id) \
            .all()
        data_ids = map(lambda tup: tup[0], data_ids_res)
        return self.add_data_to_task(task_id, data_ids)

    def get_task_data_relation_count(self, task_id):
        count = self.db.session.query(func.count(TaskDataRelation.relation_id)) \
            .filter(TaskDataRelation.task_id == task_id,
                    TaskDataRelation.is_deleted.is_(False)) \
            .first()[0]
        return count

    def remove_data_from_task(self, relation_id):
        self.db.session.query(TaskDataRelation) \
            .filter(TaskDataRelation.relation_id == relation_id) \
            .update({"is_deleted": True, })
        self.db.session.commit()
